package org.sampleSpringFramework.aop;

import net.sf.cglib.proxy.MethodInterceptor;
import org.sampleSpringFramework.aop.annotation.Aspect;
import org.sampleSpringFramework.aop.annotation.Order;
import org.sampleSpringFramework.aop.aspect.AspectInfo;
import org.sampleSpringFramework.aop.aspect.DefaultAspect;
import org.sampleSpringFramework.aop.aspect.MultiAspectMethodInterceptor;
import org.sampleSpringFramework.aop.aspect.MyEnhancer;
import org.sampleSpringFramework.core.BeanContainer;
import org.sampleSpringFramework.util.ValidationUtil;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * 说明：完成AOP的具体功能，为目标类的目标方法织入切面逻辑
 * 例如：
 * 1、标记了Aspect注解的类为切面类
 * 2、切面类的属性value决定了哪些类的方法会进行增强
 * 3、doAop执行具体的增强逻辑
 */
public class AspectWeaver {
	// 1、容器（用来获取容器中的bean等信息）
	private BeanContainer beanContainer;

	// 2、构造函数（先创建容器，再启动AOP的逻辑）
	public AspectWeaver(){
		this.beanContainer = BeanContainer.getInstance();
	}

	/**
	 * 3、执行AOP的逻辑
	 */
	public void doAop(){
		// 1、扫描所有包含@Aspect注解的切面类到容器中
		Set<Class<?>> aspectClassSet = beanContainer.getClassesByAnnotation(Aspect.class);
		// 2、将切面类按照不同的切面目标进行划分（我们的切面类只能作用在类上，属性是注解类型）
		// 比如，Aspect1作用于controller注解，Aspect2作用于service注解
		// key：controller、service注解等、value：作用于这些注解上的切面类
		Map<Class<? extends Annotation>, List<AspectInfo>> categoryMap = new HashMap<>();
		if(ValidationUtil.isEmpty(aspectClassSet)) return;//如果没有任何切面类，就不用执行AOP的逻辑
		for (Class<?> clazz: aspectClassSet){
			if(verify(clazz)){//校验切面类的合法性
				category(categoryMap,clazz);
			}
			else{
				throw new RuntimeException("请检查该类是否是满足以下的规定：" +
						"1、该类必须被@Aspect、@order标签标记" +
						"2、该类必须是DefaultAspect抽象类的子类" +
						"3、该类的属性值不能是其Aspect.class");
			}
		}
		if(ValidationUtil.isEmpty(categoryMap)) return;
		// 3、为容器中标有指定注解的类织入Aspect的逻辑
		for(Class<? extends Annotation> clazz: categoryMap.keySet()){
			doWeaveForClasses(clazz,categoryMap.get(clazz));
		}
	}

	/**
	 * 为容器中标有指定注解的类织入Aspect的逻辑
	 * @param clazz
	 * @param aspectInfos
	 */
	private void doWeaveForClasses(Class<? extends Annotation> clazz, List<AspectInfo> aspectInfos) {
		// 1、从容器中获取所有类的实例（比如所有标记的了Controller注解的类）
		Set<Class<?>> classSet = beanContainer.getClassesByAnnotation(clazz);
		// 2、为所有实例创建代理对象（使用自定义的cglib的方式创建动态代理对象）
		for (Class<?> targetClass: classSet){
			MethodInterceptor methodInterceptor = new MultiAspectMethodInterceptor(targetClass, aspectInfos);
			Object proxy = MyEnhancer.create(targetClass, methodInterceptor);
			// 3、用代理对象替换容器中的目标对象
			beanContainer.addBean(targetClass,proxy);
		}
	}


	/**
	 * 校验切面类的合法性
	 * 1、该类必须被@Aspect、@order标签标记（这样才能够转换成AspectInfo对象，从而对切面进行排序）
	 * 2、该类必须是DefaultAspect抽象类的子类（这样才能够继承里边的before、after等等方法）
	 * 3、该类的属性值不能是其Aspect.class（即切面类不能作用于切面类上，会导致死循环）
	 * @param clazz 切面类
	 * @return
	 */
	private boolean verify(Class<?> clazz) {
		return clazz.isAnnotationPresent(Aspect.class) &&
				clazz.isAnnotationPresent(Order.class) &&
				DefaultAspect.class.isAssignableFrom(clazz) &&
				clazz.getAnnotation(Aspect.class).value() != Aspect.class;
	}

	/**
	 * 对切面类进行分类，存放到categoryMap中
	 * @param categoryMap 分好类的切面集合：key（注解的类型），value（作用于该注解的所有切面类列表）
	 * @param clazz 切面类
	 */
	private void category(Map<Class<? extends Annotation>, List<AspectInfo>> categoryMap, Class<?> clazz) {
		// 1、构造AspectInfo实例
		Order orderTag = clazz.getAnnotation(Order.class);
		DefaultAspect bean = (DefaultAspect)beanContainer.getBean(clazz);
		AspectInfo aspectInfo = new AspectInfo(orderTag.value(),bean);
		// 2、将其进行分类，放入categoryMap中
		Aspect aspectTag = clazz.getAnnotation(Aspect.class);
		if(!categoryMap.containsKey(aspectTag.value())){
			List<AspectInfo> list = new ArrayList<>();
			list.add(aspectInfo);
			categoryMap.put(aspectTag.value(),list);
		}else{
			List<AspectInfo> aspectInfoList = categoryMap.get(aspectTag.value());
			aspectInfoList.add(aspectInfo);
		}
	}

}
